
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * The controller handles actions taken in the program.
 * 
 * @author Corvus, Janne
 * 
 */
public class MatrixCalculatorController {

    double[][] matrixA;
    double[][] matrixB;
    double[][] concurrent;
    double[][] serial;
    long start, end;
    boolean isLogger;
    Result result;

    /**
     * Sets the controller ready for use.
     *
     * @param doLog
     *            true if a logfile is desired.
     */
    public MatrixCalculatorController(boolean doLog) {
        matrixB = null;
        matrixA = null;
        concurrent = null;
        serial = null;
        isLogger = doLog;

        if (isLogger) {
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            String filename = "logfile_" + df.format(new Date()) + ".csv";
            try {
                result = new Result(filename);
            } catch (IOException ex) {
                System.out.println("File creation failed: " + filename);
            }
        }
    }

    /**
     * Prints a matrix to console. Only usable for very, very small matrices.
     * @param matrix
     */
    private static void printMatrix(double[][] matrix) {
        for (int y = 0; y < matrix.length; y++) {
            for (int x = 0; x < matrix[y].length; x++) {
                System.out.print(matrix[y][x] + "\t");
            }
            System.out.println();
        }
    }

    /**
     * Compares matrix a with matrix b and checks if they're the same.
     *
     * @param a
     *            first matrix
     * @param b
     *            second matrix
     * @return true if matrices are the same
     */
    private static boolean compareMatrices(double[][] a, double[][] b) {
        boolean matricesSame = true;
        if (a.length != b.length) {
            return false;
        }
        for (int y = 0; y < a.length; y++) {
            for (int x = 0; x < a[y].length; x++) {
                if (a[y].length != b[y].length) {
                    return false;
                }
                if (Math.abs(a[y][x] - b[y][x]) < 0.0001) {
                    return false;
                }
            }
        }
        return matricesSame;
    }

    /**
     * Initializes the program with random matrices of specified sizes.
     *
     * @param rows1
     * @param columns1
     * @param rows2
     * @param columns2
     */
    public void initialize(int rows1, int columns1, int rows2, int columns2) {
        if (columns2 != rows1) {
            throw new IllegalArgumentException(
                    "Matrices must be of size m*p and p*n");
        }
        this.matrixA = RandomMatrix.randomMatrix(rows1, columns1);
        this.matrixB = RandomMatrix.randomMatrix(rows2, columns2);

        result.rows1 = rows1;
        result.columns1 = columns1;

        result.rows2 = rows2;
        result.columns2 = columns2;

    }

    /**
     * Initializes the program by getting matrices read from their XML files.
     *
     * @param matrixAFile
     *            Filename for the first matrix
     * @param matrixBFile
     *            Filename for the second matrix
     */
    public void initialize(String matrixAFile, String matrixBFile) {
        try {
            this.matrixA = MatrixReader.readKerola(matrixAFile);
            this.matrixB = MatrixReader.readKerola(matrixBFile);
        } catch (FileNotFoundException e) {
            System.err.println(e);
            System.exit(1);
        }
        result.rows1 = matrixA.length;
        result.rows2 = matrixB.length;
        result.columns1 = matrixA[0].length;
        result.columns2 = matrixB[0].length;
    }

    /**
     * Calculates the result matrix using concurrency.
     */
    public void doConcurrent(int threads) {
        start = System.nanoTime();
        MatrixCalculator mc = new MatrixCalculator(matrixA, matrixB);
        mc.setThreads(threads);
        concurrent = mc.calculate();
        end = System.nanoTime();

        if (isLogger) {
            result.threadsUsed = mc.getThreads();
            result.concurrentTime = (end - start) / 1000000;
        }
    }

    /**
     * Calculates the result matrix using serial calculation.
     */
    public void doSerial() {
        start = System.nanoTime();
        SerialMatrixCalculator smc = new SerialMatrixCalculator(matrixA, matrixB);
        serial = smc.calculate();
        end = System.nanoTime();

        if (isLogger) {
            result.serialTime = (end - start) / 1000000;
        }
        try {
            MatrixWriter.writeBinary(serial, "serial_result.bin");
        } catch (IOException ex) {
            System.err.println("Serial result file could not be written!");
            System.exit(15);
        }
    }

    /**
     * Writes down the result matrix in an .xml file. Also writes the time
     * results to resultfile.
     */
    public void doFinish() {
        if (compareMatrices(concurrent, serial)) {
            /*
             * try { MatrixWriter.writeXML(concurrent, "resultmatrix.xml"); }
             * catch (IOException ex) { System.err.println("Error on write:");
             * System.err.println(ex.getMessage()); }
             */
        } else {
            System.out.println("Matrices different!");
        }

        if (isLogger) {
            result.toFile();
        }
    }

    /**
     * Closes the result file.
     */
    public void doEnd() {
        result.close();
    }

    void loadSerialResult(String filename) {
        try {
            serial = MatrixReader.readBinary(filename);
        } catch (FileNotFoundException ex) {
            System.err.println("File not found: " + filename);
        } catch (IOException ex) {
            System.err.println("Could not read file: " + filename);
        }
    }
}
